import configparser, re, base64, hashlib, json, os
from collections import Iterable
import inspect
from decimal import Decimal

import os

__regex = re.compile(r"^[a-zA-Z_][a-zA-Z_0-9]*$")
__regex_md5 = re.compile(r"^[0-9a-f]{8}(-?[0-9a-f]{4}){3}-?[0-9a-f]{12}$", re.I)
__value_types = (int, float, bool, complex, str, Decimal)


class __translate(object):
    mapping = None

    @classmethod
    def get(cls, *strings):
        if not strings:
            return None

        if cls.mapping is None:
            path = os.getcwd() + os.sep + "lang" + os.sep + "backstage" + os.sep + 'zh_CN.conf'
            if not os.path.exists(path):
                cls.mapping = {}
            else:
                cf = configparser.ConfigParser()
                cf.read(path, "utf-8")
                cls.mapping = dict((v.replace("$", "%"), k) for (k, v) in cf.items("info"))
        from tools.globalvar import get_i18nvalue as s

        if len(strings) == 1:
            if isinstance(strings[0], str):
                key = cls.mapping.get(strings[0])
                return strings[0] if key is None else s(key) or strings[0]
            if isinstance(strings[0], (list, set, tuple)):
                strings = strings[0]
        arr = []
        for string in strings:
            key = cls.mapping.get(string)
            arr.append(string if key is None else s(key) or string)
        return arr if isinstance(strings, list) else tuple(arr)


def isfunction(object):
    return inspect.isfunction(object) or inspect.ismethod(object)


def to_obj(value, include_function=False):
    if isinstance(value, dict):
        class _(dict):
            def __str__(self):
                return str(to_dict(self))

            def __setattr__(self, key, value):
                self[key] = value

            def __getattr__(self, item):
                return self.get(item)

        top = _()
        for i, j in value.items():
            if __regex.match(i) is not None and (include_function or not isfunction(j)):
                setattr(top, i, to_obj(j))
        return top
    elif isinstance(value, Iterable) and not isinstance(value, str):
        return [to_obj(x) for x in value if include_function or not isfunction(x)]
    else:
        return value if not isfunction(value) else None


def to_dict(value, include_function=False):
    if hasattr(value, "__serialization__"):
        method = getattr(value, "__serialization__")
        if inspect.ismethod(method) and method.__code__.co_argcount == 1:
            return str(method())
    if isinstance(value, dict):
        return dict((i, to_dict(j)) for i, j in value.items() if not include_function or not isfunction(j))
    elif value is None or isinstance(value, __value_types):
        return value
    elif isinstance(value, Iterable):
        return [to_dict(j) for j in value if not include_function or not isfunction(j)]
    else:
        x = {}
        for name in dir(value):
            if not name.startswith('_'):
                v = getattr(value, name)
                if include_function or not isfunction(v):
                    x[name] = to_dict(v)
        return x


def to_json_string(model):
    try:
        return json.dumps(model, ensure_ascii=False)
    except TypeError:
        return json.dumps(to_dict(model), ensure_ascii=False)


def to_json_dict(string):
    return json.loads(string)


def to_json_obj(string):
    return to_obj(json.loads(string))


class Digest(object):
    def __init__(self, protocol, bytes, string):
        self.bytes = bytes
        self._string = string
        self.protocol = protocol

    def _get_string(self):
        if isfunction(self._string):
            self._string = self._string(self)
        return self._string

    def __str__(self):
        return self.string

    def __eq__(self, other):
        if isinstance(other, Digest):
            return self.protocol == other.protocol and self.bytes == other.bytes
        if isinstance(other, str):
            return self.string == other
        return tuple(self.bytes) == tuple(other)

    def __serialization__(self):
        return self.string

    string = property(_get_string)


def encode_base64(content, encoding=None):
    if isinstance(content, str):
        content = content.encode('utf-8')
    elif isinstance(content, Digest):
        content = content.bytes
    return Digest("base64", base64.b64encode(content), lambda x: str(x.bytes, encoding or 'utf-8'))


def decode_base64(content, encoding=None):
    if isinstance(content, Digest):
        content = content.bytes
    return Digest("base64", base64.b64decode(content), lambda x: str(x.bytes, encoding or 'utf-8'))


def to_md5(content):
    if isinstance(content, str):
        content = content.encode('utf-8')
    elif isinstance(content, Digest):
        content = content.bytes
    md5 = hashlib.md5()
    md5.update(content)
    return Digest("md5", md5.digest(), md5.hexdigest())


def file_to_md5(path, batch=4096):
    if batch < 0:
        batch = 4096
    if not os.path.isfile(path):
        return
    md5 = hashlib.md5()
    with open(path, 'rb') as f:
        while True:
            b = f.read(batch)
            if not b:
                break
            md5.update(b)
    return Digest("md5", md5.digest(), md5.hexdigest())


def md5_to_bytes(md5string):
    if isinstance(md5string, Digest):
        return md5string.bytes
    if isinstance(md5string, bytes):
        return md5string
    elif not isinstance(md5string, str):
        raise TypeError("参数 md5string 必须是字符串")
    result = []
    s = ""
    for i in range(len(md5string)):
        if md5string[i] == '-':
            continue
        s += md5string[i]
        if i % 2 != 0:
            int_hex = int(s, 16)
            result.append(int_hex)
            s = ""
    return tuple(result)


def verify(content, md5_or_base64):
    is_md5 = (isinstance(md5_or_base64, Digest) and md5_or_base64.protocol == "md5") or (
            isinstance(md5_or_base64, str) and __regex_md5.match(md5_or_base64))

    if is_md5:
        return verify_md5(content, md5_or_base64)
    else:
        return verify_base64(content, md5_or_base64)


def verify_base64(content, b64):
    if isinstance(content, Digest):
        content = content.bytes if content.protocol == "md5" else to_md5(content)
    else:
        content = to_md5(content)
    return content == decode_base64(b64).bytes


def verify_md5(content, md5):
    if isinstance(content, Digest):
        content = tuple(content.bytes) if content.protocol == "md5" else to_md5(content)
    else:
        content = to_md5(content)
    return content == tuple(md5_to_bytes(md5))


def translate(string, *args):
    if not string:
        return None
    if not args or isinstance(string, (list, tuple, set)):
        return __translate.get(string)
    return __translate.get(string) % __translate.get(args)


def to_string(value):
    if hasattr(value, "__serialization__"):
        method = getattr(value, "__serialization__")
        if inspect.ismethod(method) and method.__code__.co_argcount == 1:
            return str(method())
    if value is None or isinstance(value, __value_types):
        return str(value)
    if isinstance(value, type):
        return str(value).replace("<class '", "<").replace("'>", ">")
    elif isinstance(value, Iterable):
        return ", ".join([to_string(j) for j in value if not isfunction(j)])
    else:
        return str(value)


# 测试代码
if __name__ == "__main__":
    # val = {
    #     "id": 1,
    #     "map": {
    #         "x": "name",
    #     },
    #     "arr": [
    #         {
    #             "o": "o"
    #         }
    #     ]
    # }
    # obj = to_obj(val)
    # assert obj.id == 1
    # assert obj.map.x == "name"
    # assert obj.arr[0].o == "o"
    # print(to_json_string(obj))
    # print(to_json_string(val))
    # print(str(to_json_obj(to_json_string(val))))

    text = "41f65dsa4f56d6safds6a"
    b64 = encode_base64(to_md5(text))
    print(b64)
    assert verify(text, b64)

    a1 = "123456789"
    a2 = to_md5(a1)
    a3 = encode_base64(a2.bytes)
    assert verify(a1, a3.string)
    assert verify(a1, a3.bytes)
    assert verify(a1, a3)
    assert verify(a1, a2.string)
    assert verify_md5(a1, a2.bytes)
    assert verify(a1, a2)
    assert verify(a2, a3.string)
    assert verify(a2, a3.bytes)
    assert verify(a2, a3)
